package com.xh.shuati.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.xh.shuati.annotation.RateLimiter;
import com.xh.shuati.common.BaseResponse;
import com.xh.shuati.common.ErrorCode;
import com.xh.shuati.common.ResultUtils;
import com.xh.shuati.exception.BusinessException;
import com.xh.shuati.exception.ThrowUtils;
import com.xh.shuati.model.dto.postthumb.PostThumbAddRequest;
import com.xh.shuati.model.entity.RetryThumb;
import com.xh.shuati.model.entity.User;
import com.xh.shuati.model.enums.LimitTypeEnum;
import com.xh.shuati.service.PostService;
import com.xh.shuati.service.PostThumbService;
import com.xh.shuati.service.UserService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.xh.shuati.service.impl.PostThumbServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 帖子点赞接口
 *
 * @author <a href="https://github.com/canon0521">xh</a>
 */
@RestController
@RequestMapping("/post_thumb")
@Slf4j
public class PostThumbController {

    @Resource
    private PostThumbService postThumbService;

    @Resource
    private UserService userService;

    @Resource
    private PostService postService;

    /**
     * 点赞Key前缀
     */
    private static final String POST_THUMB_KEY = "pt:tb:";

    /**
     * 重试次数
     */
    private Long RETRY_COUNT = 3L;

    /**
     * 线程池参数
     */
    final int CORE_POOL_SIZE = 20;
    final int MAX_POOL_SIZE = 50;
    final int QUEUE_CAPACITY = 10000;

    /**
     * 类线程池
     */
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            30L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadPoolExecutor.CallerRunsPolicy());

    /**
     * 延迟队列
     */
    private LinkedBlockingQueue<RetryThumb> delayedQueue = null;

    //降级策略存储Redis
    @Resource
    private RedissonClient redissonClient;

    /**
     * 点赞/取消点赞
     *
     * @param postThumbAddRequest
     * @param request
     * @return resultNum 本次点赞变化数
     */
    @PostMapping("/")
    @RateLimiter(key = "doThumb",CountTime = 1,LimitCount = 5,timeUnit = RateIntervalUnit.SECONDS,limitType = LimitTypeEnum.REJECT_USER)
    @SentinelResource( value = "doThumb",blockHandler = "blockHandler",fallback = "dothumbfallback")
    public BaseResponse<Integer> doThumb(@RequestBody PostThumbAddRequest postThumbAddRequest,
            HttpServletRequest request) {
        Long postId = postThumbAddRequest.getPostId();
        ThrowUtils.throwIf(postId == null || postId <= 0, ErrorCode.PARAMS_ERROR, "帖子ID错误");
        // 登录才能点赞
        final User loginUser = userService.getLoginUser(request);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN, "用户未登录");
        int result = postThumbService.doPostThumb(postId, loginUser);

        return ResultUtils.success(result);
    }

    /**
     * 本地限流策略
     * @param postThumbAddRequest
     * @param request
     * @param ex
     * @return
     */
    public BaseResponse<Integer> blockHandler(@RequestBody PostThumbAddRequest postThumbAddRequest, HttpServletRequest request, BlockException ex){
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR,"系统压力过大,请等待");
    }

    /**
     * 降级策略
     * @param postThumbAddRequest
     * @param request
     * @param ex
     * @return
     */
    public BaseResponse<Integer> dothumbfallback(@RequestBody PostThumbAddRequest postThumbAddRequest, HttpServletRequest request, Throwable ex){
        //1.获取参数
        Long postId = postThumbAddRequest.getPostId();
        User loginUser = userService.getLoginUser(request);
        //2.登录校验
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN, "用户未登录");
        Long loginUserId = loginUser.getId();
        //3.生成RedisKey
        RetryThumb retryThumb = new RetryThumb(UUID.randomUUID().toString(), postId,loginUser);
        String key = POST_THUMB_KEY + postId + ":" + loginUserId;

        RLock redissonClientLock = redissonClient.getLock(key);
        try{
            if (redissonClientLock.tryLock(1,5, TimeUnit.SECONDS)) {
                //4.幂等判断
                if (redissonClient.getBucket(key).isExists()) {
                    return ResultUtils.success(0);
                }
                //添加键值对到Redis
                redissonClient.getBucket(key).set(1);
                //5.添加到本地延迟队列
                boolean added = delayedQueue.add(retryThumb);
                ThrowUtils.throwIf(!added, ErrorCode.OPERATION_ERROR, "点赞失败");
            }
        } catch (InterruptedException e) {
            ThrowUtils.throwIf(true, ErrorCode.OPERATION_ERROR, "降级策略中点赞失败");
        }finally {
            redissonClientLock.unlock();
        }
        return ResultUtils.success(1);
    }

    /**
     * 错峰定时任务批量执行点赞
     *
     */
    public void runRDelayBatchAdd(List<RetryThumb> retryThumbs, int count) {
        // 1. 参数准备
        final int BATCH_SIZE = 100;
        final long MAX_RETRY = RETRY_COUNT;

        try {
            // 2. 批量处理队列中的点赞任务
            List<CompletableFuture<Void>> futures = new ArrayList<>();

            while (!delayedQueue.isEmpty()) {
                // 2.1批量获取任务
                List<RetryThumb> batch = new ArrayList<>(BATCH_SIZE);
                delayedQueue.drainTo(batch, BATCH_SIZE);
                if( batch.size() == 0){
                    log.warn("没有获取到点赞任务");
                }
                // 2.2创建异步任务
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    PostThumbServiceImpl thumbService = (PostThumbServiceImpl) AopContext.currentProxy();
                    //2.3 带重试机制的批量处理
                    for (int retry = 0; retry <= MAX_RETRY; retry++) {
                        try {
                            for (RetryThumb thumb : batch) {
                                thumbService.doPostThumb(thumb.getPostId(), thumb.getUser());
                            }
                            break;
                        } catch (Exception e) {
                            if (retry == MAX_RETRY) {
                                log.error("Failed to process thumb batch after {} retries", MAX_RETRY, e);
                            } else {
                                log.warn("Retry {} for thumb batch failed, will retry", retry, e);
                                try {
                                    //延时重试
                                    Thread.sleep(1000 * (retry + 1));
                                } catch (InterruptedException ie) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                        }
                    }
                }, threadPool);
                futures.add(future);
            }

            //3.等待所有任务完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        } finally {
            //4.关闭线程池
            threadPool.shutdown();
        }
    }

}
